/**
 * @version 2007-1-5
 * @author xalinx at gmail dot com
 */
package org.nanhill.commons.pattern.flow;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * @version 2007-1-5
 * @author xalinx at gmail dot com
 * 
 */
public class XmlFlowFactory extends AbstractFlowFactory {

	private Map<String, Flow> flowDepot = new HashMap<String, Flow>();

	private String[] flowLocations;

	public void setFlowLocations(String[] flowLocations) {
		this.flowLocations = flowLocations;
	}

	private StateFactory stateFactory;

	public void setStateFactory(StateFactory stateFactory) {
		this.stateFactory = stateFactory;
	}

	private EventFactory eventFactory;

	public void setEventFactory(EventFactory eventFactory) {
		this.eventFactory = eventFactory;
	}

	private ActionFactory actionFactory;

	public void setActionFactory(ActionFactory actionFactory) {
		this.actionFactory = actionFactory;
	}

	@SuppressWarnings("unchecked")
	protected void buildFlow() throws FlowException {
		for (int i = 0; i < this.flowLocations.length; i++) {
			ActionViewFlow flow = new ActionViewFlow();
			try {
				Document xml = new SAXReader().read(this.flowLocations[i]);
				Element root = xml.getRootElement();
				String flowId = root.attributeValue("id");
				flow.setId(flowId);
				String defActionId = root.attributeValue("default-action");

				// --- create & add state
				List<Element> stateEleList = root.elements();
				for (Iterator<Element> stateEles = stateEleList.iterator(); stateEles
						.hasNext();) {
					Element stateEle = stateEles.next();
					State state = null;
					String stateType = stateEle.getName();
					String stateId = stateEle.attributeValue("id");
					if (stateType.equals("action-state")) {
						// get action id
						String actionId = stateEle.attributeValue("action");
						actionId = actionId == null ? defActionId : actionId;

						// get action method name
						String methodName = stateEle.attributeValue("method");
						methodName = methodName == null ? stateId : methodName;

						Action action = actionFactory.createAction(actionId,
								methodName);
						state = stateFactory.createActionState(stateId, action);
					} else if (stateType.equals("view-state")) {
						String viewId = stateEle.attributeValue("view");
						state = stateFactory.createViewSate(stateId, viewId);
					}
					if (null == state) {
						String errMsg = "create state error, stateId:"
								+ stateId;
						if (logger.isErrorEnabled()) {
							logger.error(errMsg);
						}
						throw new XmlFlowException(errMsg);
					}
					flow.addState(state);
				}
				// ---

				// --- get & set start state
				String startStateId = root.attributeValue("start-state");
				State startState = flow.getState(startStateId);
				if (null == startState) {
					String errMsg = "no start state.";
					if (logger.isErrorEnabled()) {
						logger.error(errMsg);
					}
					throw new XmlFlowException(errMsg);
				}
				flow.setStartState(startState);
				// ---

				// --- create & add event
				for (Iterator<Element> stateEles = stateEleList.iterator(); stateEles
						.hasNext();) {
					Element stateEle = stateEles.next();
					String stateId = stateEle.attributeValue("id");
					State state = flow.getState(stateId);
					Iterator<Element> eventEles = stateEle.elementIterator();
					for (; eventEles.hasNext();) {
						Element eventEle = eventEles.next();
						String onId = eventEle.attributeValue("on");
						String toId = eventEle.attributeValue("to");
						State to = flow.getState(toId);
						if (null == to) {
							String errMsg = "To state(id:" + toId
									+ ") not exist.";
							if (logger.isErrorEnabled()) {
								logger.error(errMsg);
							}
							throw new XmlFlowException(errMsg);
						}
						Event event = eventFactory.createEvent(state.getId()
								+ "." + to.getId(), onId, state, to);
						if (ActionState.class
								.isAssignableFrom(state.getClass())) {
							ActionState as = (ActionState) state;
							if (onId.equals("ok")) {
								as.setOkState(to);
							} else if (onId.equals("error")) {
								as.setErrorState(to);
							}
						}
						flow.addEvent(event);
					}
				}
				// ---
				this.flowDepot.put(flow.getId(), flow);
			} catch (DocumentException e) {
				String errMsg = "Read Automaton define xml file error.";
				if (logger.isErrorEnabled()) {
					logger.error(errMsg);
				}
				throw new XmlFlowException(errMsg, e);
			}
		}
	}

	public Flow getFlow(String flowId) {
		return this.flowDepot.get(flowId);
	}

}
